/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package multicastqosrouting;

import java.util.ArrayList;

/**
 *
 * @author yeison
 */
public class RoutingParticleSwarm {

    /*
     * Constructor
     */
    public RoutingParticleSwarm(int swarmSize, int iterations) {
        i_swarmSize = swarmSize;
        i_iterations = iterations;
    }
    
    public RoutingParticleSwarm(int swarmSize, int iterations, double c1, double c2, double w, int bestParticles) {
        i_swarmSize = swarmSize;
        i_iterations = iterations;
        this.c1 = c1;
        this.c2 = c2;
        this.w = w;
        this.bestParticles = bestParticles;
    }

    /*
     * Getters and Setters
     */
    public int getEliteGroup() {
        return bestParticles;
    }

    public void setEliteGroup(int bestParticles) {
        this.bestParticles = bestParticles;
    }

    public double getC1() {
        return c1;
    }

    public void setC1(double c1) {
        this.c1 = c1;
    }

    public double getC2() {
        return c2;
    }

    public void setC2(double c2) {
        this.c2 = c2;
    }

    public int getNumberOfIterations() {
        return i_iterations;
    }

    public void setNumberOfIterations(int iterations) {
        i_iterations = iterations;
    }

    public int getSwarmSize() {
        return i_swarmSize;
    }

    public void setSwarmSize(int swarmSize) {
        i_swarmSize = swarmSize;
    }

    public double getInitialVelocity() {
        return initialVelocity;
    }

    public void setInitialVelocity(double initialVelocity) {
        this.initialVelocity = initialVelocity;
    }

    public double getW() {
        return w;
    }

    public void setW(double w) {
        this.w = w;
    }

    public ArrayList<Double> getMinFitness() {
        return minFitness;
    }

    /*
     * Method: addRoutingTable
     */
    /*
     * Adds the Routing table to each one of the destination nodes
     */
    public void addRoutingTable(ArrayList<ArrayList<Integer>> routingTable) {
        routingTablesArray.add(routingTable);
    }

    /*
     * Method: addCostMatrix
     */
    /*
     * Adds cost's matrix (2D Array randomly generated or .txt file) to the
     * i_costMatrix in order to calculate fitness
     */
    public void addCostMatrix(int[][] costMatrix) {
        i_costMatrix = new int[costMatrix.length][costMatrix[0].length];
        for (int i = 0; i < costMatrix.length; i++) {
            for (int j = 0; j < costMatrix[i].length; j++) {
                i_costMatrix[i][j] = costMatrix[i][j];
            }
        }
    }
    
    public void addDelayMatrix(int[][] delayMatrix) {
        i_delayMatrix = new int[delayMatrix.length][delayMatrix[0].length];
        for (int i = 0; i < delayMatrix.length; i++) {
            for (int j = 0; j < delayMatrix[i].length; j++) {
                i_delayMatrix[i][j] = delayMatrix[i][j];
            }
        }
    }

    public void generateInitialSwarm() {
        particleLength = routingTablesArray.size();
        for (int k = 0; k < i_swarmSize; k++) {
            swarm.add(new ArrayList<Integer>());
            pbest.add(new ArrayList<Double>());//initializate ArrayList of ArrayList to the number of particles
            velocity.add(initialVelocity);
            for (int i = 0; i < particleLength; i++) {
                int routeLength = routingTablesArray.get(i).size();
                int randomRoute = (int) (Math.random() * (routeLength) + 1); // Generate a random number between 1 and the route length
                randomRoute = randomRoute - 1;
                swarm.get(k).add(randomRoute);
            }
        }
    }

    private void calculateVelocity() {
        rand1 = Math.random(); // never 1
        rand2 = Math.random();
        // select the best position ever by the particle i
        for (int i = 0; i < i_swarmSize; i++) {
            double pbest_i = pbest.get(i).get(0);
            for (int j = 0; j < pbest.get(i).size(); j++) {
                if (pbest_i > pbest.get(i).get(j)) {
                    pbest_i = pbest.get(i).get(j);
                }
            }

            newVelocity = (w * velocity.get(i)) + (c1 * rand1 * (pbest_i - swarmFitness.get(i))) + (c2 * rand2 * (gbest - swarmFitness.get(i)));
            if (newVelocity < lowerBound) {
                newVelocity = lowerBound;//***************
            }
            velocityUpdate.add(newVelocity);
        }
    }

    private void calculatePosition() {
        for (int i = 0; i < i_swarmSize; i++) {
            double s = calculateSigmoidFunction(velocityUpdate.get(i));
            for (int j = 0; j < swarm.get(i).size(); j++) {
                double rho = Math.random();
                if (rho < s) {
                    int randomIndex = (int) (Math.random() * (bestParticles) + 1); // Generate a random number between 1 and the bestParticles number
                    randomIndex = randomIndex - 1;
                    swarm.get(i).set(j, swarmBest.get(randomIndex).get(j));
                }
            }
        }
        swarmFitness.clear();
        swarmBest.clear();
        swarmFitnessBest.clear();
        velocity.clear();
        velocity.addAll(velocityUpdate);
        velocityUpdate.clear();
    }

    private void selectBestParticles() {
        swarmFitnessBest.addAll(swarmFitness);// It’s necessary to create a new ArrayList because original will be changed (change to 0.0)
        for (int i = 0; i < bestParticles; i++) {
            double lower = swarmFitnessBest.get(0);
            int position = 0;
            for (int j = 0; j < swarmFitnessBest.size(); j++) {
                if (swarmFitnessBest.get(j) < lower) {
                    lower = swarmFitnessBest.get(j);
                    position = j;
                }
            }
            swarmFitnessBest.set(position, Double.POSITIVE_INFINITY);
            orderedFitnessArray.add(lower);//******check maybe it is not neccesary**********
            swarmBest.add(new ArrayList<Integer>());
            for (int j = 0; j < particleLength; j++) {
                swarmBest.get(i).add(swarm.get(position).get(j));
            }


        }
    }

    private void orderPopulation() {
        //The whole population ordered according to the fitness. This allow to check QoS constrains the chromosomes        
        fitnessTmp.clear();
        orderedFitnessArrayQoS.clear();
        populationOrderedByFitness.clear();
        fitnessTmp.addAll(swarmFitness);// It’s necessary to create a new ArrayList because original will be changed (change to 0.0)
        for (int i = 0; i < i_swarmSize; i++) {
            double lower = fitnessTmp.get(0);
            int position = 0;
            for (int j = 0; j < fitnessTmp.size(); j++) {
                if (fitnessTmp.get(j) < lower) {
                    lower = fitnessTmp.get(j);
                    position = j;
                }
            }
            fitnessTmp.set(position, Double.POSITIVE_INFINITY);
            orderedFitnessArrayQoS.add(lower);
            populationOrderedByFitness.add(new ArrayList<Integer>());
            for (int j = 0; j < particleLength; j++) {
                populationOrderedByFitness.get(i).add(swarm.get(position).get(j));

            }

        }

    }

    private void calculateLowerFitness() {
        // calculate fitness
        int position = 0;
        for (int i = 0; i < i_swarmSize; i++) {
            double fitness = calculateFitness(swarm.get(i));
            swarmFitness.add(fitness);
            //System.out.println(fitness);
        }
        for (int i = 0; i < i_swarmSize; i++) {
            pbest.get(i).add(swarmFitness.get(i));
        }

        //find lower fitness        
        double lower = swarmFitness.get(0);
        for (int i = 0; i < swarmFitness.size(); i++) {
            if (swarmFitness.get(i) < lower) {
                lower = swarmFitness.get(i);
                position = i;
            }
        }
        gbest = lower;
        bestParticle.clear();
        bestParticle.addAll(swarm.get(position));
        //System.out.println("********************GBest: " + gbest + "*******************");
        minFitness.add(lower);
    }

    private double calculateFitness(ArrayList<Integer> particle) {
        double fitness = 0;
        double sum = 0;
        for (int i = 0; i < particleLength; i++) {
            int counter = 0;
            int sourceNode = 0;
            int destinationNode = 0;
            int route = particle.get(i);
            String path = routingTablesArray.get(i).get(route).toString();
            //System.out.println(path);
            for (int j = 0; j < routingTablesArray.get(i).get(route).size(); j++) {
                if (j % 2 == 0) {
                    sourceNode = routingTablesArray.get(i).get(route).get(j);
                    counter++;
                }

                if (j % 2 == 1) {
                    destinationNode = routingTablesArray.get(i).get(route).get(j);
                    counter++;
                }

                if (counter % 2 == 0) {
                    fitness += i_costMatrix[sourceNode][destinationNode];

                }
            }
        }
        return fitness;
    }
    
    private boolean checkDemand(ArrayList<Integer> subroute) {
        double delay = 0;
        double delaymi = 0;
        double delaymj = 0;
        double jitter;
        double delaytmp = 0;
        double sum = 0;
        for (int i = 0; i < subroute.size(); i++) {
            int counter = 0;
            int sourceNode = 0;
            int destinationNode = 0;
            int route = subroute.get(i);
            String path = routingTablesArray.get(i).get(route).toString();
            //System.out.println(path);

            for (int j = 0; j < routingTablesArray.get(i).get(route).size(); j++) {
                if (j % 2 == 0) {
                    sourceNode = routingTablesArray.get(i).get(route).get(j);
                    counter++;
                }

                if (j % 2 == 1) {
                    destinationNode = routingTablesArray.get(i).get(route).get(j);
                    counter++;
                }

                if (counter % 2 == 0) {
                    delay += i_delayMatrix[sourceNode][destinationNode];
                    delaytmp += i_delayMatrix[sourceNode][destinationNode];
                }
            }


            if (i == 0) {
                delaymi = delaytmp;
            }
            if (i == 1) {
                delaymj = delaytmp;
            }
            delaytmp = 0;
        }
        jitter = delaymi - delaymj;
        if (jitter <= delayVariation && delay <= delayMulticast) {
            return true;
        } else {
            return false;
        }
    }

    private double calculateSigmoidFunction(double velocity) {
        double sigmoid = (1 / (1 + (Math.exp(-velocity))));
        return sigmoid;
    }
    
    private void getRouteFromParticle(ArrayList<Integer> particle) {
        for (int i = 0; i < particle.size(); i++) {
            int route = particle.get(i);
            lowerPathQoS += routingTablesArray.get(i).get(route).toString() + " ";
        }
    }
    private void getLowerCostPath (ArrayList<Integer> particle) {
        for (int i = 0; i < particle.size(); i++) {
            int route = particle.get(i);
            lowerPath += routingTablesArray.get(i).get(route).toString() + " ";
        }        
    }

    public void performParticleRouting() {
        for (int i = 0; i < i_iterations; i++) {
            calculateLowerFitness();
            calculateVelocity();
            selectBestParticles();
            orderPopulation();
            calculatePosition();
            try {
                if (i == i_iterations - 1) {
                    boolean qos;
                    int counter = 0;
                    do {
                        qos = checkDemand(populationOrderedByFitness.get(counter));
                        counter++;
                    } while (qos == false);
                    getRouteFromParticle(populationOrderedByFitness.get(counter - 1));
                }

            } catch (Exception e) {
                pathFound = false;
            }
            if (i == i_iterations - 1) {
                getLowerCostPath(bestParticle);
            }
            
        }
    }
    
    public void printMinFitness() {
        //System.out.println();
        //System.out.println("Min Fitness: ");
        for (int i = 0; i < minFitness.size(); i++) {
            System.out.print(minFitness.get(i) + "\t");
        }
    }
    
     public void printLowerCostPathQoS() {
        if (pathFound == true) {
            System.out.println();
            System.out.println("Lower Cost Path Found with QoS constrains: (Delay <= " + delayMulticast + " - Delay Variation <= " + delayVariation + ")");
            System.out.println(lowerPathQoS);
        } else {
            System.out.println("No path found with QoS constrains (Delay <= " + delayMulticast + " - Delay Variation <= " + delayVariation + ")");
        }
    }
     
     public void printLowerCostPath () {
         System.out.println("The Lower Cost Path is: ");
         System.out.println(lowerPath);
     }
    //instance variables
    private double w = 0.729; // inertia weight
    private double c1 = 1.49445; //cognitive weight
    private double c2 = 1.49445; //social weight
    private double initialVelocity = 0;
    private double lowerBound = 0;
    private int bestParticles = 20;
    private int i_swarmSize;
    private int i_iterations;
    private ArrayList<ArrayList<ArrayList<Integer>>> routingTablesArray = new ArrayList<ArrayList<ArrayList<Integer>>>();
    private int[][] i_costMatrix;
    private int[][] i_delayMatrix;
    private int particleLength;
    private ArrayList<ArrayList<Integer>> swarm = new ArrayList<ArrayList<Integer>>();
    private ArrayList<ArrayList<Integer>> swarmBest = new ArrayList<ArrayList<Integer>>();
    private ArrayList<Integer> bestParticle = new ArrayList<Integer>();
    private ArrayList<Double> swarmFitness = new ArrayList<Double>();
    private ArrayList<Double> swarmFitnessBest = new ArrayList<Double>();
    private ArrayList<Double> minFitness = new ArrayList<Double>();
    private double gbest;
    private double rand1; //random values
    private double rand2; //random values
    private ArrayList<ArrayList<Double>> pbest = new ArrayList<ArrayList<Double>>();
    private double newVelocity;
    private ArrayList<Double> velocity = new ArrayList<Double>();
    private ArrayList<Double> velocityUpdate = new ArrayList<Double>();
    private ArrayList<Double> orderedFitnessArray = new ArrayList<Double>();
    private ArrayList<Double> fitnessTmp = new ArrayList<Double>();
    private ArrayList<Double> orderedFitnessArrayQoS = new ArrayList<Double>();
    private ArrayList<ArrayList<Integer>> populationOrderedByFitness = new ArrayList<ArrayList<Integer>>();
    private double delayMulticast = 30;
    private double delayVariation = 20;
    private String lowerPathQoS = "";
    private String lowerPath = "";
    private boolean pathFound = true;
}
